﻿using System;
using System.Collections.Generic;
using System.Linq;
using Maria.Common.Aspects;

namespace RG.Common.Provider
{
    public interface IDAOTransactionExtension
    {
        object Enter();
        void Leave(object cookie, bool commit);
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)]
    public class BaseTransactionAttribute : GenericMethodWrapper
    {
        protected static object DoInvoke(WrapperParameter[] AttributeParameters, object[] MethodParameters, InvokeOriginal Invoker, bool isFake)
        {
            var providers = (AttributeParameters != null)
                                ? AttributeParameters
                                      .Select(p => p as ProviderAttribute)
                                      .Where(p => p != null)
                                      .Select(p => p.GetProvider())
                                : null;

            if (providers == null) providers = EmptyList;

            var BaseAttr = (AttributeParameters != null)
                                ? AttributeParameters
                                      .Select(a => a as DAOTransactionAttribute)
                                      .Where(a => a != null)
                                      .FirstOrDefault()
                                : null;
            var ScopeTimeout = (BaseAttr != null && BaseAttr.Timeout != 0)
                                   ? TimeSpan.FromSeconds(BaseAttr.Timeout)
                                   : (TimeSpan?)null;

            var extensions = (AttributeParameters != null)
                                ? AttributeParameters
                                      .Select(p => p as IDAOTransactionExtension)
                                      .Where(p => p != null)
                                      .ToList()
                                : null;

            using (var holder = DAOTransactionHolder.GetHolder(providers, isFake, ScopeTimeout))
            {
                bool commit = false;
                List<object> cookies = null;
                try
                {
                    if (extensions != null)
                        cookies = extensions.Select(e => e.Enter()).ToList();
                    var result = Invoker(MethodParameters);
                    commit = true;
                    return result;
                }
                finally
                {
                    try
                    {
                        if (cookies != null)
                        {
                            for (int i = 0; i < extensions.Count; i++)
                                extensions[i].Leave(cookies[i], commit);
                        }
                    }
                    finally
                    {
                        DAOTransactionHolder.PutHolder(holder, commit);
                    }
                }
            }
        }

        static readonly List<IDAOProvider> EmptyList = new List<IDAOProvider>();
    }

    public class DAOTransactionAttribute : BaseTransactionAttribute
    {
        public DAOTransactionAttribute() { }
        public DAOTransactionAttribute(int timeout) { Timeout = timeout; }

        new public static object OnInvoke(WrapperParameter[] AttributeParameters, object[] MethodParameters, InvokeOriginal Invoker, object WrappedInstance, RuntimeTypeHandle WrappedType, string WrappedMethod)
        {
            return DoInvoke(AttributeParameters, MethodParameters, Invoker, false);
        }

        internal int Timeout { get; set; }
    }

    public class FakeTransactionAttribute : BaseTransactionAttribute
    {
        new public static object OnInvoke(WrapperParameter[] AttributeParameters, object[] MethodParameters, InvokeOriginal Invoker, object WrappedInstance, RuntimeTypeHandle WrappedType, string WrappedMethod)
        {
            return DoInvoke(AttributeParameters, MethodParameters, Invoker, true);
        }
    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
    [LinkToWrapper(typeof(DAOTransactionAttribute))]
    [LinkToWrapper(typeof(FakeTransactionAttribute))]
    public abstract class ProviderAttribute : WrapperParameter
    {
        public abstract IDAOProvider GetProvider();
    }

    public class TransactionService<TProvider> where TProvider : IDAOProvider
    {
        static public IDAOTransaction DAOSession { get { return DAOTransactionHolder.GetSession(typeof(TProvider)); } }
    }
}